/*
* ============================================================================
*  Name        : sea_association.h
*  Part of     : Finite state machine utilities
*  Description : object definition
*  Version     : %version: %
*
*
* ============================================================================
*/
#ifndef SEA_ASSOCIATION_H
#define SEA_ASSOCIATION_H

#include "sea_defines.h"
#include "sea_types.h"
#include "sea_object.h"

/**
 * @file sea_association.h
 * @brief Associations are used to link objects together. One object can be associated with unlimited number of qualities
 * or other objects
 *
 * @ingroup AssociationUtilities
 */

/**
 * @namespace sea
 */
namespace sea
{
    class Object;
    class aref;

    /**
     * @class Association sea_association.h "sea_association.h"
     * @brief Associations are used to link objects together. One object can be associated with unlimited number of qualities
     * or other objects
     *    @code
     *          // An object
     *          aref apple("apple");
     *
     *          // Objects qualities
     *          aref color("color");
     *
     *          aref smell("smell");
     *
     *          // Adding apple attributes
     *          apple += color;
     *          apple += smell;
     *
     *          if ( apple( color ) ){  }
     *          if ( apple( smell ) ){  }
     *
     *          // Remove them
     *          apple -= color;
     *          apple -= smell;
     *
     *          if ( !apple( color ) ){  }
     *          if ( !apple( smell ) ){  }
     *         ...
     *  @endcode
     * Association = { enumerator | object | { enumerator & object } } <br>
     * Enumerator & object pair intended for the case when an object associated with itself <br>
     * Example:
     *  @li Word: "ABBA"
     *  @li A -> B -> B -> A
     *  @li A(0,K) -> B(1,K) -> B(2,K) -> A(3,K)
     * where K is a id of the actual word that is association object
     *
     * @ingroup AssociationUtilities
     */
    class Association
    {
    public:
        enum AssociationType
        {
            A_ANY,                  ///< Association is void*
            A_ENUM,                 ///< Association is a simple enumerator
            A_STRING,               ///< Association is a const char*
            A_OBJECT,               ///< Association is an Object
            A_ENUM_OBJECT           ///< Association is enumerating object
        };

        /**
         * @struct EnumObject
         * @brief Enumerated object type. Used to group 'letters' in 'words'
         */
        struct EnumObject
        {
            long m_enum_value;      ///< Enumerator
            Object* m_object;       ///< Object pointer
        };

        Association( );
        Association( aref& obj );
        Association( Object* obj );
        Association( const char* str );
        Association( int id );
        Association( int id, Object* obj );

        bool is_any( ) { return m_type == A_ANY; }
        bool is_enum( ) { return m_type == A_ENUM; }
        bool is_string( ) { return m_type == A_STRING; }
        bool is_object( ) { return m_type == A_OBJECT; }
        bool is_enum_object( ) { return m_type == A_ENUM_OBJECT; }

        EnumObject get_enum_object( ) { return m_id.m_enum_object; }

        template< typename T > T* as( ) { return reinterpret_cast< T* >( data( ) ); }

        template< typename T > T* as_enum_object( )
        {
            if ( !is_enum_object( ) )
            {
                return NULL;
            }
            return reinterpret_cast< T* >( m_id.m_enum_object.m_object );
        }

        long get_enum( )
        {
            if ( is_enum_object( ) )
            {
                return m_id.m_enum_object.m_enum_value;
            }
            return -1L;
        }

        Object* get_object( )
        {
            if ( is_object( ) )
            {
                return m_id.m_object;
            }
            return NULL;
        }

        const char* get_string( )
        {
            if ( is_string( ) )
            {
                return m_id.m_string;
            }
            return NULL;
        }

        void next( )
        {
            if ( is_enum_object( ) )
            {
                m_id.m_enum_object.m_enum_value++;
            }
        }

        void prev( )
        {
            if ( is_enum_object( ) )
            {
                m_id.m_enum_object.m_enum_value--;
            }
        }

        bool operator==( Association& id )
        {
            if ( m_type == A_ANY )
            {
                return true;
            }
            if ( m_type != id.m_type )
            {
                return false;
            }
            if ( m_type == A_ENUM )
            {
                return m_id.m_enum_value == id.m_id.m_enum_value;
            }
            if ( m_type == A_STRING )
            {
                return m_id.m_string == id.m_id.m_string;
            }
            if ( m_type == A_OBJECT )
            {
                return m_id.m_object == id.m_id.m_object;
            }
            if ( m_type == A_ENUM_OBJECT )
            {
                return m_id.m_enum_object.m_object == id.m_id.m_enum_object.m_object &&
                       m_id.m_enum_object.m_enum_value == id.m_id.m_enum_object.m_enum_value;
            }
            return false;
        }

        bool operator==( long id )
        {
            return m_type == A_ANY ||
                ( m_type == A_ENUM && m_id.m_enum_value == id );
        }

        bool operator==( char* id )
        {
            return m_type == A_ANY ||
                ( m_type == A_STRING && m_id.m_string == id );
        }

        bool operator==( Object* id )
        {
            return m_type == A_ANY ||
                ( m_type == A_OBJECT && m_id.m_object == id );
        }

        bool operator==( EnumObject enum_object ) {
            return m_type == A_ANY ||
                ( m_type == A_ENUM_OBJECT &&
                  m_id.m_enum_object.m_object == enum_object.m_object &&
                  m_id.m_enum_object.m_enum_value == enum_object.m_enum_value );
        }

        void reset( long enum_value = 0 )
        {
            if ( is_enum_object( ) )
            {
                m_id.m_enum_object.m_enum_value = enum_value;
            }
        }

    private:
        void* data( );
        const void* const_data( );

    private:
        union
        {
            void* m_any;                    ///< Any ppointer
            long m_enum_value;              ///< Enumerator value
            const char* m_string;           ///< String pointer
            Object* m_object;               ///< Object pointer
            EnumObject m_enum_object;       ///< Enumerated object
        } m_id;
        AssociationType m_type;
    };
}

#endif
